//鸭子类型检测：在使用第三方库或模块时，往往无法了解它们的所有类型信息。使用鸭子类型，我们可以只关注它们是否满足我们需要的接口即可。满足就算相等
//子类型可以赋予给父类型，从结构的角度出发，ts比较的不是类型的名称，而是结构上的属性和方法

//将一个值赋予给另外一个值，可以产生兼容性
//----------------------------------------1、基础类型的兼容性：有相同的属性和方法就可以赋值----------------------------------------
let obj:{
    toString():string;
}
let str:string='dddd' //也存在toString方法
obj=str//str为什么可以赋予给obj,应为obj要求有toString方法，str有，所以可以赋予
//从安全的角度考虑，你需要的属性我都满足，只能访问已经存在的属性，不存在的无法访问

//----------------------------------------2、接口的兼容性：子可以赋给父----------------------------------------
interface IPerson{
    name:string,
    age:number
}
interface IAnimal{
    name:string,
    age:number
    address:string
}
//!，感叹号表示，一定存在这个值
let person!:IPerson
let animal!:IAnimal
person=animal//animal为什么可以赋值给person，应为animal的类型都包括person，
//在后台返回的数据中，我们可以预先定义好接口类型，多的属性也可以赋值给这个类型

//----------------------------------------3、函数的兼容性：参数可以少不能多----------------------------------------
let s1=(a:string,b:string):string|number=>a
let s2=(a:string):100=>100
s1=s2//s2为什么可以赋值给s1，函数的参数可以少不能多和返回值都符合条件

function forEach<T>(array:T[],callback:(item:T,i:number,arr:T[])=>void){
    for (let index = 0; index < array.length; index++) {
        const element = array[index];
        callback(element,index,array)
    }
}
forEach([1,2,3],(item,i,arr)=>{

})

//----------------------------------------函数的逆变与协变：参数是逆变，返回值是协变----------------------------------------

class Person{
    house(){

    }
}

class Child extends Person{
    car(){
        
    }
}

class Grandson extends Child{
    money(){
        
    }
}
function fn1(callback:(instance:Child)=>Child){
    let child=new Child();
    let ins=callback(child)
    return ins;
}
//参数是逆变的，参数可以传父，就是参数比定义的少是可以的-参数少传
fn1((instance:Child)=>{
    return new Child();
})
fn1((instance:Person)=>{
    return new Child();
})

function fn2(callback:(instance:Child)=>Child){
    let child=new Child();
    let ins=callback(child)
    return ins;
}
//返回值是协变的，必须包含定一个，可以比定义的多-返回值多
fn2((instance:Child):Child=>{
    return new Grandson();
})


//推导公式
//1、参数逆变-参数的属性和方法要少
type Arg<T>=(arg:T)=>any;
type ArgTypeChild=Arg<Child>//表示定义一个函数，参数必须是Chile
const ff:ArgTypeChild=()=>{

}
ff(new Child)
type ArgTypeBoolean=ArgTypeChild extends Arg<Grandson> ?true : false//true

//2、返回值协变-返回值的属性和方法要多

type Return<T>=(arg:any)=>T; //表示返回值必须是T，传入的类型

const rr:Return<Child>=()=>{
    return new Child
}
rr(new Child)

type ReturnTypeBoolean=Return<Child> extends Return<Person> ?true : false//true


interface MyArray<T>{
    concat(...args:T[]):T[]//不会对参数进行逆变检测=》推荐
    // concat:(...args:T[])=>T[]//会对参数进行逆变检测=》不推荐
}
let a1!:MyArray<Person>
let a2!:MyArray<Child>
a1=a2


//----------------------------------------4、泛型兼容性----------------------------------------
interface TT<T>{

}
let o1:TT<string>=''
let o2:TT<number>=22
o1=o2//o2为什么可以赋值给o1呢，因为ts比较的是结构，o1和o2的结构都是{}空对象

//----------------------------------------5、枚举（enum）不具备兼容----------------------------------------
enum E1{

}
enum E2{
    
}
let e1!:E1
let e2!:E2
// e1=e2 //不能赋值

//----------------------------------------6、类的兼容性----------------------------------------
class A{
   name!:string 
}

class B{
    name!:string 
    age!:number
 }
//  let a11:B=new A()//比较的是属性，不符合就不兼容，B中的属性在A中有些没有
 let a21:A=new B()

 class A1{
    name!:string 
 }
 
 class B1{
     private name!:string 
     age!:number
  }
//   let a3:A1=new B1()//如果类中存在私有属性或者受保护的属性，则不兼容



//----------------------------------------7、标称----------------------------------------
//ts中比较类型的时候，比较的是结构上的属性和方法
//如果属性和方法都满足则兼容，有一些比较特殊
//基础类型和对象类型的兼容、接口的兼容、泛型的兼容、枚举的兼容、类的兼容
//在其他语言中存在标称类型（根据名称来区分）

type BTC=number
type UST=number
let btc:BTC=1000
let ust:UST=1000
btc=ust//ts是根据接口来比较的，他们都是number类型，所以能互相赋值，但是他们是两种类型货币，不让赋值，应该怎么处理？
type Nominal<T,K extends string>=T & {_tag:K}
type BTC1=Nominal<number,'btc'>
type UST1=Nominal<number,'ust'>
let btc1:BTC1=1000 as BTC1
let ust2:UST1=1000 as UST1
// btc1=ust2// 此时他们就不能互相赋值，接口不一样
function getVal(val:BTC1|UST1){
    if(val._tag==='btc'){//可辨识类型
        return 111
    }else{
        return 222
    }

}
const a=getVal(btc1)
export {}
